package com.doan.covua.gamelogic;


import java.util.ArrayList;
import java.util.List;

import android.util.Log;

import com.doan.covua.ChessActivity;
import com.doan.covua.ChessBoard;
import com.doan.covua.ChessEngine;
import com.doan.covua.GUIInterface;
import com.doan.covua.GameMode;
import com.doan.covua.gamelogic.GameTree.Node;


public class ChessController {

    // Debug
    public static final String   TAG            = "ChessController";
    private static final boolean D              = true;

    PgnToken.PgnTokenReceiver    gameTextListener;
    GUIInterface                 gui;
    GameTree                     tree;
    GameMode                     gameMode;
    TimeControl                  timeController;
    ChessEngine                  chessEngine;

    Thread                       computerThread;
    boolean                      pendingDrawOffer;
    private boolean              addFirst;

    private boolean              isDoMove       = false;

    private String               strMoveToSend;

    private Move                 movePromo      = null;

    public static boolean        isPlayAsWhite  = true;
    public static boolean        isSinglePlayer = true;

    public ChessController(GUIInterface gui, PgnToken.PgnTokenReceiver gamTextListener) {
        this.gui = gui;
        this.gameTextListener = gamTextListener;
        timeController = new TimeControl();
    }

    public final void newGame(GameMode gameMode, boolean isSinglePlayer) {
        ChessController.isSinglePlayer = isSinglePlayer;
        this.gameMode = gameMode;
        if (isSinglePlayer) {

            if (chessEngine == null) {
                chessEngine = new ChessEngine();
            }
        }
        tree = new GameTree(gameTextListener);

    }

    public final void startGame(boolean isPlayAsWhite) {
        ChessController.isPlayAsWhite = isPlayAsWhite;
        if (isSinglePlayer) {
            updateComputeThreads(true);
        }
        setSelection();
        updateGUI();
    }

    private final void updateGUI() {

        // updateMoveList();
        String str = "";

        // get game state
        GameState gameState = tree.getGameState();
        if (gameState != GameState.ALIVE) {
            str = getGameStateString(gameState);
            // Log.v("game state","" + str);
            gui.setStatusString(str);
            gui.showGameResult();
        }
        StringBuilder sb = new StringBuilder();
        if (tree.currentNode != tree.rootNode) {
            tree.goBack();
            if (D) {
                Log.v(TAG, "vao goBack()");
            }
            Position pos = currPos();

            List<Move> prevVarList = tree.variations();
            for (int i = 0; i < prevVarList.size(); i++) {
                if (i > 0) {
                    sb.append(' ');
                }
                if (i == tree.currentNode.defaultChild) {
                    sb.append("<b>");
                }
                sb.append(TextIO.moveToString(pos, prevVarList.get(i), false));
                if (i == tree.currentNode.defaultChild) {
                    sb.append("</b>");
                }
            }

            tree.goForward(-1);
        }
        gui.setPosition(currPos(), null, tree.variations());
    }

    private final void updateComputeThreads(boolean clearPV) {
        boolean computersTurn = !humansTurn();
        if (!computersTurn) {
            ChessActivity.stopThinkingAnim();
            ChessActivity.updateBoardState();
            ChessActivity.updateCheckStatus(currPos());
            stopComputerThinking();
        } else {
            startComputerThinking();

        }

    }

    private final synchronized void startComputerThinking() {
        if (computerThread == null) {
            // ss = new SearchStatus();
            final Pair<Position, ArrayList<Move>> ph = getUCIHistory();
            final Position currPos = new Position(currPos());
            final boolean haveDrawOffer = haveDrawOffer();
            long now = System.currentTimeMillis();
            final int wTime = timeController.getRemainingTime(true, now);
            final int bTime = timeController.getRemainingTime(false, now);
            final int inc = timeController.getIncrement();
            final int movesToGo = timeController.getMovesToTC();

            computerThread = new Thread(new Runnable() {

                public void run() {

                    final String cmd = chessEngine.doSearch(ph.first, ph.second, currPos, haveDrawOffer, wTime, bTime, inc, movesToGo);

                    // TODO Auto-generated method stub
                    gui.runOnUIThread(new Runnable() {

                        public void run() {
                            // TODO Auto-generated method stub
                            Position oldPos = new Position(currPos());
                            processString(cmd);
                            // listener.clearSearchInfo();
                            stopComputerThinking();
                            updateComputeThreads(true);
                            setSelection();
                            setAnimMove(oldPos, getLastMove(), true);
                            ChessActivity.drawCapturedPiece(oldPos, getLastMove());
                            ChessBoard.setLastMove(getLastMove(), true);
                            updateGUI();
                        }
                    });
                }
            });
            // listener.clearSearchInfo();
            chessEngine.shouldStop = false;
            computerThread.start();
            updateGUI();
        }
    }

    private final synchronized void stopComputerThinking() {
        if (computerThread != null) {
            chessEngine.stopSearch();
            try {
                computerThread.join();
            } catch (InterruptedException ex) {
                System.out.printf("Could not stop computer thread%n");
            }
            computerThread = null;
            updateGUI();
        }
    }

    /**
     * Return the last position and list of the moves
     */
    public final Pair<Position, ArrayList<Move>> getUCIHistory() {
        Pair<List<Node>, Integer> ml = tree.getMoveList();

        List<Node> moveList = ml.first;
        Position pos = new Position(tree.startPos);
        ArrayList<Move> mList = new ArrayList<Move>();
        Position currPos = new Position(pos);
        UndoInfo ui = new UndoInfo();
        int nMoves = ml.second;
        for (int i = 0; i < nMoves; i++) {
            Node n = moveList.get(i);
            mList.add(n.move);
            currPos.makeMove(n.move, ui);
            if (currPos.halfMoveClock == 0) {
                pos = new Position(currPos);
                mList.clear();
            }
        }
        return new Pair<Position, ArrayList<Move>>(pos, mList);
    }

    private final void handleDrawCmd(String drawCmd) {
        Position pos = tree.currentPos;
        if (drawCmd.startsWith("rep") || drawCmd.startsWith("50")) {
            boolean rep = drawCmd.startsWith("rep");
            Move m = null;
            String ms = null;
            int firstSpace = drawCmd.indexOf(" ");
            if (firstSpace >= 0) {
                ms = drawCmd.substring(firstSpace + 1);
                if (ms.length() > 0) {
                    m = TextIO.stringToMove(pos, ms);
                }
            }
            boolean valid;
            if (rep) {
                valid = false;
                UndoInfo ui = new UndoInfo();
                int repetitions = 0;
                Position posToCompare = new Position(tree.currentPos);
                if (m != null) {
                    posToCompare.makeMove(m, ui);
                    repetitions = 1;
                }
                Pair<List<Node>, Integer> ml = tree.getMoveList();
                List<Node> moveList = ml.first;
                Position tmpPos = new Position(tree.startPos);
                if (tmpPos.drawRuleEquals(posToCompare)) {
                    repetitions++;
                }
                int nMoves = ml.second;
                for (int i = 0; i < nMoves; i++) {
                    Node n = moveList.get(i);
                    tmpPos.makeMove(n.move, ui);
                    TextIO.fixupEPSquare(tmpPos);
                    if (tmpPos.drawRuleEquals(posToCompare)) {
                        repetitions++;
                    }
                }
                if (repetitions >= 3) {
                    valid = true;
                }
            } else {
                Position tmpPos = new Position(pos);
                if (m != null) {
                    UndoInfo ui = new UndoInfo();
                    tmpPos.makeMove(m, ui);
                }
                valid = tmpPos.halfMoveClock >= 100;
            }
            if (valid) {
                String playerAction = rep ? "draw rep" : "draw 50";
                if (m != null) {
                    playerAction += " " + TextIO.moveToString(pos, m, false);
                }
                addToGameTree(new Move(0, 0, 0), playerAction);
            } else {
                pendingDrawOffer = true;
                if (m != null) {
                    processString(ms);
                }
            }
        } else if (drawCmd.startsWith("offer ")) {
            pendingDrawOffer = true;
            String ms = drawCmd.substring(drawCmd.indexOf(" ") + 1);
            if (TextIO.stringToMove(pos, ms) != null) {
                processString(ms);
            }
        } else if (drawCmd.equals("accept")) {
            // if (haveDrawOffer())
            // addToGameTree(new Move(0, 0, 0), "draw accept");
        }
    }

    public final boolean haveDrawOffer() {
        return tree.currentNode.playerAction.equals("draw offer");
    }

    /** get current position */
    final public Position currPos() {
        return tree.currentPos;
    }

    /** get the last move */
    public final Move getLastMove() {
        return tree.currentNode.move;
    }

    final private void setSelection() {
        Move m = getLastMove();
        int sq = (m != null) ? m.to : -1;
        gui.setSelection(sq);
    }

    /** True if human's turn to make a move. (True in analysis mode.) */
    public final boolean humansTurn() {
        return gameMode.humansTurn(currPos().whiteMove);
    }

    private void setAnimMove(Position sourcePos, Move move, boolean forward) {
        gui.setAnimMove(sourcePos, move, forward);
    }

    public final void makeHumanMove(Move m) {
        Position oldPos = new Position(currPos());
        if (doMove(m)) {
            if (isSinglePlayer) {
                stopComputerThinking();
                updateComputeThreads(true);
            }
            isDoMove = true;
            setAnimMove(oldPos, m, true);
            updateGUI();
        } else {
            isDoMove = false;
            gui.setSelection(-1);
        }
    }

    Move promoteMove;

    /**
     * Move a piece from one square to another.
     * 
     * @return True if the move was legal, false otherwise.
     */

    final private boolean doMove(Move move) {
        // generate the moves

        Position pos = currPos();
        ArrayList<Move> moves = new MoveGen().pseudoLegalMoves(pos);
        moves = MoveGen.removeIllegal(pos, moves);
        int promoteTo = move.promoteTo;
        for (Move m : moves) {
            if ((m.from == move.from) && (m.to == move.to)) {
                if ((m.promoteTo != Piece.EMPTY) && (promoteTo == Piece.EMPTY)) {
                    promoteMove = m;
                    // if (ChessActivity.s_isSinglePlayer) {
                    gui.requestPromotePiece();
                    // }

                    return false;
                }
                if (m.promoteTo == promoteTo) {
                    if (D) {
                        Log.v(TAG, "doMove - convert move to string");
                    }
                    String strMove = TextIO.moveToString(pos, m, false);
                    processString(strMove);
                    strMoveToSend = strMove;
                    // addToGameTree(m,"");
                    // resetPos(move);
                    return true;
                }
            }
        }

        // resetPos(move);
        // return true;
        return false;
    }

    /** reset squares position */
    /** temp method */
    final private Position resetPos(Move move) {
        Position pos = currPos();
        int pos_temp = pos.squares[move.from];
        pos.squares[move.from] = 0;

        Log.v("square to", "" + move.to);

        pos.squares[move.to] = pos_temp;
        Position pos1 = new Position(pos);
        return pos1;
    }

    // update for do move

    /**
     * Update the game state according to move/command string from a player.
     * 
     * @param str The move or command to process.
     * @return True if str was understood, false otherwise.
     */

    public final boolean processString(String str) {
        // do something
        // .....
        /*
         * if (str.startsWith("draw ")) { String drawCmd = str.substring(str.indexOf(" ") + 1); handleDrawCmd(drawCmd); return true; } else if
         * (str.equals("resign")) { addToGameTree(new Move(0, 0, 0), "resign"); return true; }
         */
        Move m = TextIO.UCIstringToMove(str);
        if (m != null) {
            ArrayList<Move> moves = new MoveGen().pseudoLegalMoves(currPos());
            moves = MoveGen.removeIllegal(currPos(), moves);

            boolean legal = false;
            for (int i = 0; i < moves.size(); i++) {
                if (m.equals(moves.get(i))) {
                    legal = true;
                    break;
                }
            }
            if (!legal) {
                m = null;
            }
        }
        if (m == null) {
            m = TextIO.stringToMove(currPos(), str);
        }
        if (m == null) {
            return false;
        }
        addToGameTree(m, pendingDrawOffer ? "draw offer" : "");
        return true;
    }

    private final void addToGameTree(Move m, String playerAction) {
        if (m.equals(new Move(0, 0, 0))) { // Don't create more than one null
                                           // move at a node
            List<Move> varMoves = tree.variations();
            for (int i = varMoves.size() - 1; i >= 0; i--) {
                if (varMoves.get(i).equals(m)) {
                    tree.deleteVariation(i);
                }
            }
        }

        List<Move> varMoves = tree.variations();
        boolean movePresent = false;
        int varNo;
        for (varNo = 0; varNo < varMoves.size(); varNo++) {
            if (varMoves.get(varNo).equals(m)) {
                movePresent = true;
                break;
            }
        }
        if (!movePresent) {
            String moveStr = TextIO.moveToUCIString(m);
            varNo = tree.addMove(moveStr, playerAction, 0, "", "");
        }
        int newPos = addFirst ? 0 : varNo;
        tree.reorderVariation(varNo, newPos);
        tree.goForward(newPos);
        // int remaining = timeController.moveMade(System.currentTimeMillis(),
        // !gamePaused);
        // tree.setRemainingTime(remaining);
        // updateTimeControl(true);
        pendingDrawOffer = false;
    }

    public boolean getDoMove() {
        return isDoMove;
    }

    public String getStringToMove() {
        return strMoveToSend;
    }

    // Trung add: legal move of a piece
    public ArrayList<Move> legalMoveOfSquare(int sq) {
        if (sq < 0) {
            return null;
        }
        Position pos = currPos();
        ArrayList<Move> moves = new MoveGen().pseudoLegalMovesOfSquare(pos, sq);
        moves = MoveGen.removeIllegal(pos, moves);// moves is content legal moves of square
        return moves;
    }

    /*
     * update status for gameplay
     */
    public static enum GameState {
        ALIVE, WHITE_MATE, // White mates
        BLACK_MATE, // Black mates
        WHITE_STALEMATE, // White is stalemated
        BLACK_STALEMATE, // Black is stalemated
        DRAW_REP, // Draw by 3-fold repetition
        DRAW_50, // Draw by 50 move rule
        DRAW_NO_MATE, // Draw by impossibility of check mate
        DRAW_AGREE, // Draw by agreement
        RESIGN_WHITE, // White resigns
        RESIGN_BLACK
        // Black resigns
    }

    public final String getGameStateString(GameState gameState) {

        switch (gameState) {
        case ALIVE:
            return "";
        case WHITE_MATE:
            if (isPlayAsWhite) {
                return "You Win!";
            } else {
                return "You Lose!";
            }
        case BLACK_MATE:
            if (isPlayAsWhite) {
                return "You Lose!";
            } else {
                return "You Win!";
            }
        case WHITE_STALEMATE:
        case BLACK_STALEMATE:
            return "Game Over!";
        default:
            throw new RuntimeException();
        }
    }

    public final void reportPromotePiece(int choice) {
        final boolean white = currPos().whiteMove;
        int promoteTo;
        switch (choice) {
        case 1:
            promoteTo = white ? Piece.WROOK : Piece.BROOK;
            break;
        case 2:
            promoteTo = white ? Piece.WBISHOP : Piece.BBISHOP;
            break;
        case 3:
            promoteTo = white ? Piece.WKNIGHT : Piece.BKNIGHT;
            break;
        default:
            promoteTo = white ? Piece.WQUEEN : Piece.BQUEEN;
            break;
        }
        promoteMove.promoteTo = promoteTo;
        Move m = promoteMove;
        // String strMove = TextIO.moveToString(currPos(), promoteMove, false);
        // Move m = TextIO.stringToMove(currPos(), strMove);
        promoteMove = null;
        makeHumanMove(m);
        movePromo = m;
    }

    public Move getMovePromo() {
        return movePromo;
    }

}
